<?php

namespace app\common\service;


use Endroid\QrCode\QrCode;
use think\facade\Config;
use think\facade\Log;
use think\facade\Request;

class WxPayService
{
    /**
     * @param $order_sn 订单号
     * @param $total_fee    总费用（元）
     * @param $type 订单类型  1社保账单    2套餐/续费订单    3公司增值订单   4个人补缴订单   5个人增值订单
     * @param $body 支付标语
     * @param $platform 支付平台 1：大户 2：独立户
     * @param int $pay_type 支付类型1：native 2：h5 3：jsapi
     * @param null $openid  用户openid
     * @param null $wap_url 当前域名
     * @param null $redirect_url    跳转url
     * @return array|string
     */
    public function pay($order_sn, $total_fee, $type, $body, $platform = 1,$pay_type = 1, $openid = null, $wap_url = null,$redirect_url = null)
    {
        $request = Request::instance();
        $domain = $request->domain();
        $params = [];
        // 当前时间
        $time = time();
        // 生成随机字符串
        $nonceStr = md5(uniqid());
        if ($pay_type == 1) {
            $trade_type = 'NATIVE';
        } elseif ($pay_type == 2) {
            if ($wap_url == null || $redirect_url == null)
                return ['code' => 0, 'info' => '请求失败,wap_url&redirect_url不能为null'];
            $trade_type = 'MWEB';
            $params['scene_info'] = json_encode([
                'h5_info' => [
                    'type' => 'Wap',
                    'wap_url' => $wap_url,
                    'wap_name' => $body
                ]
            ]);
        } else {
            $trade_type = 'JSAPI';
        }
        // API参数
        $params = [
            'appid' => Config::get()['wechat']['service']['appid'],
            'mch_id' => Config::get()['wechat']['merchant']['mch_id'],
            'body' => $body,
            'out_trade_no' => $order_sn,
            'attach' => json_encode(['type' => $type, 'pay_type' => $pay_type,'platform'=>$platform]),
            'nonce_str' => $nonceStr,
            'notify_url' => $domain . url('api/callback/native_notify'),  // 异步通知地址
            'spbill_create_ip' => \request()->ip(),
            'total_fee' => (int)($total_fee * 100), // 价格:单位分
            'trade_type' => $trade_type,
            'limit_pay' => 'no_credit',
        ];
        if ($openid != null) {
            $params['openid'] = $openid;
        }

        // 生成签名
        $params['sign'] = $this->makeSign($params);
        // 请求API
        $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
        $result = $this->postXmlCurl($this->toXml($params), $url);
        $prepay = $this->fromXml($result);
        Log::record($prepay, 'pay');
        // 请求失败
        if ($prepay['return_code'] === 'FAIL') {
            return ['code' => 0, 'info' => '请求失败', 'data' => $prepay['return_msg']];
        }
        if ($prepay['result_code'] === 'FAIL') {
            return ['code' => 0, 'info' => '请求失败', 'data' => $prepay['err_code_des']];
        }
        if ($pay_type == 1) {
            $qrCode = new QrCode($prepay['code_url']);
            $today = date('Y-m-d', time());
            $path = 'upload/qrcode/' . $today;
            if (!file_exists($path)) {
                mkdir($path, 0777, true);
            }
            $qrCode->save($path . '/' . $nonceStr . '.png');
            return $domain . '/' . $path . '/' . $nonceStr . '.png';
        } elseif ($pay_type == 2) {
            $pay_url = $prepay['mweb_url'].'&redirect_url='.urlencode($redirect_url);
            Log::record($pay_url,'pay_url');
            return $pay_url;
        } elseif ($pay_type == 3) {
            // 生成 nonce_str 供前端使用
            $paySign = $this->makePaySign($params['nonce_str'], $prepay['prepay_id'], $time);
            return [
                'prepay_id' => $prepay['prepay_id'],
                'nonceStr' => $params['nonce_str'],
                'timeStamp' => (string)$time,
                'paySign' => $paySign,
                'order_sn' => $order_sn
            ];
        }
    }

    /**
     * 以post方式提交xml到对应的接口url
     * @param $xml
     * @param $url
     * @param int $second
     * @return mixed
     */
    public function postXmlCurl($xml, $url, $second = 30)
    {
        $ch = curl_init();
        // 设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);//严格校验
        // 设置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        // 要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        // post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        // 运行curl
        $data = curl_exec($ch);
        curl_close($ch);
        return $data;
    }

    /**
     * 输出xml字符
     * @param $values
     * @return bool|string
     */
    public function toXml($values)
    {
        if (!is_array($values)
            || count($values) <= 0
        ) {
            return false;
        }

        $xml = "<xml>";
        foreach ($values as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }

    /**
     * 将xml转为array
     * @param $xml
     * @return mixed
     */
    public function fromXml($xml)
    {
        // 禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
    }

    /**
     * 生成签名
     * @param $values
     * @return string 本函数不覆盖sign成员变量，如要设置签名需要调用SetSign方法赋值
     */
    public function makeSign($values)
    {
        //签名步骤一：按字典序排序参数
        ksort($values);
        $string = $this->toUrlParams($values);
        //签名步骤二：在string后加入KEY
        $string = $string . '&key=' . Config::get()['wechat']['merchant']['mch_key'];
        //签名步骤三：MD5加密
        $string = md5($string);
        //签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }

    /**
     * 生成paySign
     * @param $nonceStr
     * @param $prepay_id
     * @param $timeStamp
     * @return string
     */
    public function makePaySign($nonceStr, $prepay_id, $timeStamp)
    {
        $data = [
            'appId' => Config::get()['wechat']['service']['appid'],
            'nonceStr' => $nonceStr,
            'package' => 'prepay_id=' . $prepay_id,
            'signType' => 'MD5',
            'timeStamp' => $timeStamp,
        ];

        //签名步骤一：按字典序排序参数
        ksort($data);

        $string = $this->toUrlParams($data);

        //签名步骤二：在string后加入KEY
        $string = $string . '&key=' . Config::get()['wechat']['merchant']['mch_key'];

        //签名步骤三：MD5加密
        $string = md5($string);

        //签名步骤四：所有字符转为大写
        $result = strtoupper($string);

        return $result;
    }

    /**
     * 格式化参数格式化成url参数
     * @param $values
     * @return string
     */
    public function toUrlParams($values)
    {
        $buff = '';
        foreach ($values as $k => $v) {
            if ($k != 'sign' && $v != '' && !is_array($v)) {
                $buff .= $k . '=' . $v . '&';
            }
        }
        return trim($buff, '&');
    }
}